package ar.uba.fi.tecnicas.juego;

import java.awt.*;
import java.util.*;
import java.util.List;

import ar.uba.fi.tecnicas.fabrica.*;
import ar.uba.fi.tecnicas.fabrica.Terreno.ModoAdquisicion;
import ar.uba.fi.tecnicas.fabrica.exceptions.*;
import ar.uba.fi.tecnicas.juego.exceptions.*;
import ar.uba.fi.tecnicas.vista.IVistaFabrica;

public class Jugador extends Observable
{
	private String nombreJugador;
	
	private Finanzas finanzas;
	private Fabrica fabrica;
	
	private Laboratorio laboratorio;
	// Porcentaje de la ganancia diaria que se destina al laboratorio
	private double porcentajeInvestigacion;
	
	public Jugador(String nombre, double capital)
	{
		this.nombreJugador = nombre;
		this.finanzas = new Finanzas(capital);
		this.fabrica = new Fabrica();
		this.laboratorio = new Laboratorio();
		this.porcentajeInvestigacion= 0;
		
		/// Los productos con capital de inversion cero son los conocidos
		for(TipoProducto tipo : Juego.instancia.getTiposProducto())
		{
			if(tipo.getCapitalInversion() == 0)
			{
				this.fabrica.agregarTipoProductoConocido(tipo);
			}
			else
			{
				this.laboratorio.agregarProductoADescubrir(tipo);
			}
		}
	}
	
	public String getNombre()
	{
		return(this.nombreJugador);
	}
	
	public double getCapital()
	{
		return this.finanzas.getCapital();
	}
	
	public boolean bancarrota()
	{
		return this.getCapital() <= 0;
	}
	
	public boolean exito(double montoCapitalFinal) 
	{
		return this.finanzas.getCapital() >= montoCapitalFinal;
	}
	
	public Maquina comprarMaquina(int indexTipoMaquina, Point posicion, int indexLinea) throws Exception
	{
		if(this.fabrica.getTerreno() == null)
		{
			throw new FabricaSinTerrenoException();
		}

		TipoMaquina tipo = Juego.instancia.getTipoMaquina(indexTipoMaquina);
		LineaProduccion linea = this.fabrica.getLineaProduccion(indexLinea);
		
		this.capitalSuficiente(tipo.getPrecio());
		
		PosicionTerreno posicionTerreno = new PosicionTerreno(posicion);
		
		// TODO: Crear MaquinaBuilder para poder manejar la creacion Maquina normal/control calidad desde un solo lugar.
		Maquina maquina = null;
		
		if(tipo.esCalidad())
		{
			maquina = new MaquinaControlCalidad((TipoMaquinaCalidad)tipo, posicionTerreno);
		}
		else
		{
			maquina = new Maquina(tipo, posicionTerreno);
		}
			 
		this.finanzas.agregarGastoEfectivo(tipo.getPrecio());
		
		linea.agregarItem(maquina);
		
		this.fabrica.getTerreno().setPosicion(posicionTerreno);
		
		this.setChanged();
		this.notifyObservers();
		
		return maquina;
	}
	
	public void moverMaquina(Point posicionOrigen, Point posicionDestino) throws Exception
	{
		if(!this.posicionTerrenoOcupada(posicionOrigen))
		{
			throw new PosicionTerrenoLibreException(posicionOrigen);
		}
		
		if(this.posicionTerrenoOcupada(posicionDestino))
		{
			throw new PosicionTerrenoOcupadaException(posicionDestino);
		}
		
		PosicionTerreno posicionTerrenoOrigen = this.fabrica.getTerreno().getPosicion(posicionOrigen);
		PosicionTerreno posicionTerrenoDestino = new PosicionTerreno(posicionDestino);
		
		ItemLineaProduccion item = posicionTerrenoOrigen.getItem();
		
		posicionTerrenoDestino.setItem((IConectable)item);
		
		this.fabrica.getTerreno().setPosicion(posicionTerrenoDestino);
		item.setPosicion(posicionTerrenoDestino);
		this.fabrica.getTerreno().resetPosicion(posicionOrigen);
	}
	
	public void conectarMaquinas(Point posicionOrigen, Point posicionDestino) throws Exception 
	{
		if(!this.posicionTerrenoOcupada(posicionOrigen))
		{
			throw new PosicionTerrenoLibreException(posicionOrigen);
		}
		
		if(!this.posicionTerrenoOcupada(posicionDestino))
		{
			throw new PosicionTerrenoLibreException(posicionDestino);
		}

		IConectable origen = (IConectable)this.fabrica.getTerreno().getPosicion(posicionOrigen).getItem();
		IConectable destino = (IConectable)this.fabrica.getTerreno().getPosicion(posicionDestino).getItem();

		double distancia = Cinta.calcularDistancia(origen, destino);
		this.capitalSuficiente(distancia * ConfiguracionJuego.instancia.getPrecioMetroCinta());
		
		LineaProduccion linea = ((ItemLineaProduccion)origen).getLineaProduccion();

		Cinta cinta = new Cinta();
		linea.agregarItem(cinta);
		
		cinta.conectar(origen, destino);
		
		this.finanzas.agregarGastoEfectivo(distancia * ConfiguracionJuego.instancia.getPrecioMetroCinta());
		
		this.setChanged();
		this.notifyObservers();
	}
	
	public void adquirirTerreno(int indexTerreno, Terreno.ModoAdquisicion modo) throws Exception 
	{		
		Terreno terreno = Juego.instancia.getTerreno(indexTerreno);

		this.capitalSuficiente(terreno.getPrecio(modo));

		this.fabrica.setTerreno(terreno, modo);

		this.finanzas.agregarGastoEfectivo(terreno.getPrecio(modo));
		
		this.fabrica.ubicarSalida(new Point(terreno.getAncho() / 2, terreno.getAlto() / 2));
		
		this.setChanged();
		this.notifyObservers();
	}
	
	public void venderTerreno() 
	{		
		double ganancia = this.fabrica.venderTerreno(
			ConfiguracionJuego.instancia.getPorcentajeVentaTerreno(),
			ConfiguracionJuego.instancia.getPorcentajeVentaMaquina()
		);

		this.finanzas.agregarGananciaEfectivo(ganancia);
		
		this.setChanged();
		this.notifyObservers();
	}
	
	public void seleccionarPrimeraMaquina(int indexLineaProduccion, Point posicion) throws Exception
	{
		LineaProduccion linea = this.fabrica.getLineaProduccion(indexLineaProduccion);
		
		PosicionTerreno posicionTerreno = this.fabrica.getTerreno().getPosicion(posicion);
		
		linea.setPrimerItem((IConectable)posicionTerreno.getItem());		
	}
	
	public void habilitarSuspenderLinea(int indexLineaProduccion)
	{
		LineaProduccion linea = this.fabrica.getLineaProduccion(indexLineaProduccion);
		
		linea.setDetenida(!linea.detenida());
	}
	
	public void agregarMateriaPrimaNecesaria (int indexLineaProduccion, int indexTipoMateriaPrima, double cantidad)  throws Exception
	{
		LineaProduccion linea = this.fabrica.getLineaProduccion(indexLineaProduccion);
		TipoMateriaPrima tipo = Juego.instancia.getTipoMateriaPrima(indexTipoMateriaPrima);
		
		MateriaPrima materiaPrima = new MateriaPrima(tipo, cantidad);
		
		linea.agregarMateriaPrimaNecesaria(materiaPrima);
	}
	
	public void arreglarMaquina (Point posicion, double porcentajeArreglo) throws Exception
	{
		if(!this.posicionTerrenoOcupada(posicion))
		{
			throw new PosicionTerrenoLibreException(posicion);
		}
		
		if(porcentajeArreglo > 0)
		{
			ItemLineaProduccion item = this.fabrica.getTerreno().getPosicion(posicion).getItem();
			
			if(item.getEspecificacion().esMaquina())
			{
				Maquina maquina = (Maquina)item;
				
				double gasto = (porcentajeArreglo / 100.0) *  maquina.getTipo().getPrecio();
				
				this.capitalSuficiente(gasto);
				
				maquina.arreglar(porcentajeArreglo);
				
				this.finanzas.agregarGasto(gasto);
				
				this.setChanged();
				this.notifyObservers();
			}
		}		
	}
	
	public void venderMaquina(Point posicion)throws Exception
	{
		if(!this.posicionTerrenoOcupada(posicion))
		{
			throw new PosicionTerrenoLibreException(posicion);
		}
		
		ItemLineaProduccion item = this.fabrica.getTerreno().getPosicion(posicion).getItem();
		
		if(item.getEspecificacion().esMaquina())
		{
			Maquina maquina = (Maquina)item;
			
			double precioVenta = maquina.getTipo().getPrecio() * maquina.getEstado();
			
			maquina.eliminar();
			
			this.fabrica.getTerreno().resetPosicion(maquina.getPosicion().getPosicion());
			
			this.finanzas.agregarGananciaEfectivo(precioVenta);
		}
	}
	
	public void desconectarSalidaMaquina(Point posicion) throws Exception
	{
		if(!this.posicionTerrenoOcupada(posicion))
		{
			throw new PosicionTerrenoLibreException(posicion);
		}
		
		ItemLineaProduccion item = this.fabrica.getTerreno().getPosicion(posicion).getItem();
		
		if(item.getEspecificacion().esMaquina())
		{
			Maquina maquina = (Maquina)item;
			
			if(maquina.getSalida() != null)
			{
				((IConector)maquina.getSalida()).desconectar();
				maquina.notifyObservers();
			}
		}
	}
	
	public void definirPorcentajeInvestigacion(double porcentaje) throws Exception 
	{
		if(porcentaje < 0)
		{
			throw new CantidadIngresadaMenorACeroException();
		}

		this.porcentajeInvestigacion = porcentaje;
		
		this.setChanged();
		this.notifyObservers();
	}
	
	public double getPorcentajeInvestigacion()
	{
		return this.porcentajeInvestigacion;	
	}
	
	private void capitalSuficiente(double gasto) throws Exception
	{
		if(this.finanzas.getCapital() < gasto)
		{
			throw new CapitalInsuficienteException();			
		}
	}
	
	public List<TipoProducto> getTiposProductoConocidos()
	{
		return this.fabrica.getTiposProductoConocidos();
	}


	public void cerrarBalanceDiario() 
	{
		double ganancia = this.fabrica.calcularGanancia();
		
		this.finanzas.agregarGanancia(ganancia);

		double gananciaNeta = this.finanzas.gananciaDiariaNeta();
		
		if(gananciaNeta > 0)
		{				
			double capitalLaboratorio = gananciaNeta * (this.porcentajeInvestigacion / 100.0);

			this.finanzas.agregarGasto(capitalLaboratorio);
			
			this.laboratorio.investigar(this.fabrica, capitalLaboratorio);
		}
		
		this.finanzas.cerrarBalance();
		
		this.setChanged();
		this.notifyObservers();
	}

	public void producir() throws Exception
	{
		List<MateriaPrima> pedidos = this.fabrica.getMateriaPrimaNecesaria();

		// TODO: Agregar CapitalInsuficienteException
		if(pedidos.size() > 0 && Mercado.instancia.capitalSuficiente(pedidos, this.finanzas.getCapital()))
		{
			this.finanzas.agregarGasto(Mercado.instancia.efectuarCompra(pedidos));
			
			this.setChanged();
			this.notifyObservers();
		}
		
		this.fabrica.producir();
	}

	public void crearLineaProduccion(String nombre) throws Exception 
	{
		this.fabrica.crearLineaProduccion(nombre);
		
		this.notifyObservers();
	}

	public List<LineaProduccion> getLineasProduccion() 
	{
		return this.fabrica.getLineasProduccion();
	}

	public Dimension getDimensionTerreno() 
	{
		return new Dimension(
			this.fabrica.getTerreno().getAncho(), 
			this.fabrica.getTerreno().getAlto()
		);
	}

	public boolean posicionTerrenoOcupada(Point posicion) 
	{
		return this.fabrica.getTerreno().posicionOcupada(posicion);
	}

	public void agregarVistaFabrica(IVistaFabrica vista) 
	{
		this.fabrica.addObserver((Observer)vista);
		
		vista.setFabrica(this.fabrica);
	}

	public void pagarAlquiler() throws Exception 
	{
		if(this.fabrica.getTerreno().getModoAdquisicion() == ModoAdquisicion.Alquiler)
		{
			double alquiler = this.fabrica.getTerreno().getPrecio(ModoAdquisicion.Alquiler);
			
			this.capitalSuficiente(alquiler);
			
			this.finanzas.agregarGasto(alquiler);
		}		
	}

	public double getCapitalInvertido() 
	{
		return this.laboratorio.getCapitalInvertido();
	}
}
